﻿// FluentViewModel (http://fluentviewmodel.codeplex.com/)
//
// Copyright © 2010 Kialco Group 
//
// The contents of this file are subject to the Mozilla Public
// License Version 1.1 (the "License"); you may not use this file
// except in compliance with the License. You may obtain a copy of
// the License at http://www.mozilla.org/MPL/
// 
// Software distributed under the License is distributed on an 
// "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
// implied. See the License for the specific language governing
// rights and limitations under the License.

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq.Expressions;
using System.Windows.Input;
using System.Reflection;
using FluentValidation;

namespace FluentViewModel
{
    /// <summary>
    /// View model configuration.
    /// </summary>
    /// <typeparam name="TViewModel">The type of the view model.</typeparam>
    public abstract class ConfigureViewModel<TViewModel> : IConfigurationProvider
    {
        private readonly ViewModelConfiguration configuration;
        private bool nextBool;

        protected ConfigureViewModel()
        {
            configuration = new ViewModelConfiguration(typeof(TViewModel));
            nextBool = true;
        }

        /// <summary>
        /// Inverts the next boolean.
        /// </summary>
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        public ConfigureViewModel<TViewModel> Not
        {
            get
            {
                nextBool = !nextBool;
                return this;
            }
        }

        /// <summary>
        /// Configures the validator.
        /// </summary>
        /// <typeparam name="TValidator">The type of the validator.</typeparam>
        public void Validator<TValidator>()
            where TValidator : AbstractValidator<TViewModel>
        {
            configuration.ValidatorType = typeof(TValidator);
        }

        /// <summary>
        /// Configures the controller.
        /// </summary>
        /// <typeparam name="TController">The type of the controller.</typeparam>
        public void Controller<TController>()
        {
            configuration.ControllerType = typeof(TController);
        }

        /// <summary>
        /// Configures the transience of the view model.
        /// </summary>
        public void Transient()
        {
            configuration.Transient = nextBool;
            nextBool = true;
        }

        /// <summary>
        /// Identifies the property as requiring <see cref="INotifyPropertyChanged"/> behavior.
        /// </summary>
        /// <typeparam name="TProperty">The type of the property.</typeparam>
        /// <param name="expression">The expression.</param>
        public void Notify<TProperty>(Expression<Func<TViewModel, TProperty>> expression)
        {
            configuration.Notifiers.Add(GetPropertyName(expression));
        }

        /// <summary>
        /// Identifies the property as a command that must be wired to the controller.
        /// </summary>
        /// <param name="expression">The expression.</param>
        public void Command(Expression<Func<TViewModel, ICommand>> expression)
        {
            configuration.Commands.Add(GetPropertyName(expression));
        }

        /// <summary>
        /// Specifies an initialization action for the property.
        /// </summary>
        /// <typeparam name="TProperty">The type of the property.</typeparam>
        /// <param name="expression">The expression.</param>
        /// <param name="initializer">The initializer.</param>
        public void Initialize<TProperty>(Expression<Func<TViewModel, TProperty>> expression, Expression<Func<TProperty>> initializer)
        {
            var lambda = initializer.Compile();
            configuration
                .Initializers
                .Add(viewModel => viewModel.PropertySet(GetPropertyName(expression), lambda.Invoke()));
        }

        /// <summary>
        /// Specifies an initialization action for the property.
        /// </summary>
        /// <typeparam name="TProperty">The type of the property.</typeparam>
        /// <param name="expression">The expression.</param>
        /// <param name="initializer">The initializer.</param>
        public void Initialize<TProperty>(Expression<Func<TViewModel, TProperty>> expression, Expression<Func<IServiceLocator, TProperty>> initializer)
        {
            var lambda = initializer.Compile();
            configuration
                .Initializers
                .Add(viewModel => viewModel.PropertySet(GetPropertyName(expression), lambda.Invoke(configuration.ServiceLocator)));
        }

        IConfiguration IConfigurationProvider.GetConfiguration()
        {
            return configuration;
        }

        private static string GetPropertyName<TProperty>(Expression<Func<TViewModel, TProperty>> expression)
        {
            var memberExpression = expression.Body as MemberExpression;
            if (memberExpression == null)
                throw new InvalidOperationException("Expression must be a member expression.");
            var propertyInfo = memberExpression.Member as PropertyInfo;
            if (propertyInfo == null)
                throw new InvalidOperationException("Member expression must reference a property.");
            return propertyInfo.Name;
        }
    }
}